如果人在台北,能不能先規劃週末去台南玩的散步路線?
這是一個很常見的情境。例如使用者可能會想搜尋:

解決這個問題的核心,並非限制使用者的操作,而是將體驗拆解為兩種動態模式:「規劃模式」與「散步模式」。
當搜尋的起點或目標路線距離裝置目前位置過遠時,系統會自動進入「規劃模式」;反之,若裝置已靠近路線,則自動切換或允許開啟「散步模式」。
當裝置距離目標路線尚有一段距離時,代表使用者目前並沒有要「立刻出發」。此時 App 會鎖定在規劃模式。
在規劃模式下,使用者依然可以:
但不會顯示「開始散步」的按鈕,因為目前還沒有真正抵達這條路線。
只有當裝置已經靠近推薦路線時,才能夠點擊開始散步的按鈕。
當使用者抵達推薦路線附近後,才允許點擊「開始散步」進入散步模式。
不過還有一個問題:
系統要怎麼知道使用者真的已經抵達推薦路線?
直覺的作法是比較「目前位置」與「路線起點」的距離,不過思考了一下就發現行不通。
假設推薦的是一條 3 公里的河濱步道,有些人可能會從起點開始,也有人會直接從另一個入口進入。若要求一定要回到起點,即使人已經站在步道旁邊,也可能因為距離起點太遠,而始終無法開始散步。
因此判斷標準不應該是「距離起點有多遠」,而是「距離整條推薦路線的最短距離有多遠」。
Google Routes 回傳的路線,其實不是一串經緯度,而是一段 Encoded Polyline:
abcedfghijk......
這段字串無法直接繪製,需要先進行解碼以取得座標點陣列:
Google Routes
↓
Encoded Polyline
↓
decodePolyline()
↓
Route Points[{lat, lng}, ...]
解碼完成後會得到一系列的 Route Points,每個點都代表路線折線上的頂點座標。
有了 Route Points 後,最直接的方式是遍歷所有點,找出距離目前裝置最近的頂點。
使用者目前的位置
●
Route Points │ (最短距離)
●────●────●────●────●
↑
最近的 Route Point
基礎實作:
// 計算兩經緯度之間的實際地面距離(Haversine Formula),單位:公尺
function distanceMeters(pt1, pt2) {
const R = 6371000; // 地球平均半徑 (m)
const dLat = (pt2.lat - pt1.lat) * (Math.PI / 180);
const dLng = (pt2.lng - pt1.lng) * (Math.PI / 180);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(pt1.lat * (Math.PI / 180)) *
Math.cos(pt2.lat * (Math.PI / 180)) *
Math.sin(dLng / 2) *
Math.sin(dLng / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
// 遍歷所有 Route Points 找出最近距離
function distanceToNearestRoutePoint(deviceLocation, routePoints) {
if (!routePoints || routePoints.length === 0) return Infinity;
let nearest = Infinity;
for (const point of routePoints) {
nearest = Math.min(
nearest,
distanceMeters(deviceLocation, point)
);
}
return nearest;
}
// 判斷是否滿足開始條件
const canStart =
distanceToNearestRoutePoint(deviceLocation, route.points) <= START_DISTANCE;
僅計算「點到頂點」的距離在多數情況下可行,但在極長直線路段(如 1 公里無轉彎的大道)上,Google Polyline 可能只會留頭尾兩個點。若使用者剛好站在這條路的中段,距離兩端頂點都很遠,系統就會誤判。
若要追求更嚴謹的判定,可引入幾何庫(如 Turf.js),計算使用者位置到路線線段(Line Segment)的垂直最短距離:
import pointToLineDistance from '@turf/point-to-line-distance';
import { point, lineString } from '@turf/helpers';
function distanceToNearestRoute(deviceLocation, routePoints) {
const userPt = point([deviceLocation.lng, deviceLocation.lat]);
const lineCoords = routePoints.map(p => [p.lng, p.lat]);
const routeLine = lineString(lineCoords);
// 計算點到折線的最短垂直距離 (回傳 km 轉為 m)
const distanceKm = pointToLineDistance(userPt, routeLine, { units: 'kilometers' });
return distanceKm * 1000;
}
